# -*- coding: UTF-8 -*-

import re

__all__ = [ 'parse_style' ]

# Style property
class style_property(object):
	def __init__(self,name,attr,priority,pseudo):
		self.name = name
		self.attr = attr
		self.priority = priority
		self.pseudo = pseudo
	
# base class of selector, stores properties definition in selector.
class base_selector(object):
	def __init__(self):
		# initialize properties
		self.properties = []
		self.pseudo = None
	
	# apply styles to components	
	def apply(self,candidates):
		# make sure candidates should be corrent
		candidates = self.match(candidates)
		
		if len(candidates) == 0:
			print "no candidates applied"
			return

		p = self.priority((0,0,0))
		p = p[0]*100 + p[1]*10 + p[2]
		for comp in candidates:
			for name,value,important in self.properties:
				priority = p
				if important:
					priority += 1000
	
				# see if property is attached
				cat = comp.style.get(self.pseudo)
				if cat == None:
					cat = {}
					comp.style[self.pseudo] = cat

				if cat.has_key(name):
					# compare priority
					org = cat[name]
					if org.priority > priority:
						continue
					org.attr = value
					org.priority = priority
				else:
					cat[name] = style_property(name,value,priority,self.pseudo)

# matches typename of component.
class element_selector(base_selector):
	def __init__(self,name):
		super(element_selector,self).__init__()
		self.affect_child = False
		self.name = name.replace('|','.')
		if self.name.endswith('|'):
			self.name = self.name[:-1]
			self.affect_child = True
		
		print "created element selector", self.name
	
	# priority
	def priority(self,pri):
		return (pri[0],pri[1],pri[2]+1)
	
	# match typename
	def match(self,candidates):
		filtered = []
		for i in candidates:
			if i.typename == self.name:
				filtered.append(i)
			else:
				t1 = element_selector.__get_type(self.name)
				if t1:
					t2 = i.template
					if self.affect_child:
						if issubclass(t1,t2):
							filtered.append(i)
					elif t1 == t2:
						filtered.append(i)
		return filtered
	
	# get type from modules
	@staticmethod
	def __get_type( name ):
		index = name.rfind('.')
		if index == -1 :
			module = "xeres"
			type = name
		else:
			# Get type from specific module.
			index = name.rfind('.')
			module = name[:index]
		type = name[index+1:]
		exp = "from " + module + " import " + type
		try:
			d = {}
			exec exp in d
			return d[type]
		except:
			print "Type (", name, ") not found!"
			return None

# matches class property.
class class_selector(base_selector):
	def __init__(self,name):
		super(class_selector,self).__init__()
		self.name = name
		print "created class selector", self.name
	
	# priority
	def priority(self,pri):
		return (pri[0],pri[1]+1,pri[2])
		
	# match style class name
	def match(self,candidates):
		filtered = []
		for i in candidates:
			if i.stylename == self.name:
				filtered.append(i)
		return filtered
		
# matches component id property.
class id_selector(base_selector):
	def __init__(self,name):
		super(id_selector,self).__init__()
		self.name = name
		print "created id selector", self.name

	# priority
	def priority(self,pri):
		return (pri[0]+1,pri[1],pri[2])
		
	# match component id
	def match(self,candidates):
		filtered = []
		for i in candidates:
			if i.id == self.name:
				print "match found" , i.id , self.name
				filtered.append(i)
		return filtered

# match selector by expression & rules.
class rule_selector(base_selector):
	def __init__(self,sel,prop,match,expr):
		super(rule_selector,self).__init__()
		print "created detail selector", prop, match, expr
		self.selector = sel
		self.prop = prop
		self.expr = expr
		self.tag = match
	
	# priority
	def priority(self,pri):
		return self.selector.priority(pri)

	# match selector by rule
	def match(self,candidates):
		filtered = self.selector.match(candidates)
		if len(filtered) == 0:
			return []
		result = []
		print "match!" , self.prop
		for i in filtered:
			prop = i.get_property(self.prop)
			if prop:
				print "match '%s' '%s'" %(prop,self.expr)
				if self.tag == None:
					if prop == self.expr:
						result.append(i)
				elif self.tag == '^':
					if prop.startswith(self.expr):
						result.append(i)
				elif self.tag == '$':
					if prop.endswith(self.expr):
						result.append(i)
				else:
					assert self.tag == '*'
					if prop.find(self.expr) != -1:
						result.append(i)
		return result

# match contextual selector.
class context_selector(base_selector):
	def __init__(self,seq):
		super(context_selector,self).__init__()
		print "created context selector"
		self.seq = seq

	# priority
	def priority(self,pri):
		for i in self.seq:
			pri = i.priority(pri)
		return pri
		
	# match by context
	def match(self,candidates):
		filtered = self.seq[0].match(candidates)
		for i in xrange(1,len(self.seq)):
			if len(filtered) == []:
				return []
			current = self.seq[i]
			filtered = self.relation(current,filtered)
		return filtered

	def relation(self,current,candidates):
		# list children from candidates
		result = []
		for i in candidates:
			if i.is_container():
				children = i.list_children()
				result += current.match(children)
		return result

# specific pseudo class.
class pseudo_selector(base_selector):
	def __init__(self,node,attr):
		super(pseudo_selector,self).__init__()
		print "created pseudo selector", attr
		self.node = node
		# override pseudo
		self.pseudo = attr

	# priority
	def priority(self,pri):
		return self.node.priority(pri)
	
	# match pseudo class
	def match(self,candidates):
		filtered = self.node.match(candidates)
		return filtered

# group a serial of selectors.
class group_selector(base_selector):
	def __init__(self,group):
		super(group_selector,self).__init__()
		print "created group selector"
		self.group = group
	
	# priority
	def priority(self,pri):
		return (0,0,0)
	
	# match selector by rule
	def match(self,candidates):
		matched = []
		for i in self.group:
			m = i.match(candidates)
			matched += m
		return list(set(matched))


def expand_candidates(comp):
	candidates = [comp]
	if comp.is_container():
		for i in comp.children:
			candidates += expand_candidates(i)
	return candidates


# comment pattern
COMMENT = '(\/\*([^\*]|[\*][^\/])*[\*][\/])'
_re_comment = re.compile(COMMENT)

# decl pattern
DECL = '[^\{]*[\{][^\}]*[\}]'
_re_decl = re.compile(DECL)

# parse selector text to rules
def parse_style(text):
	# eliminate comment
	global _re_comment, _re_decl
	text = _re_comment.sub( '', text ).strip()
	
	# preprocess
	text = re.sub('\s+',' ',text)
	decls = _re_decl.findall( text )
	
	if len(decls) == 0:
		return

	selectors = []
	for i in decls:
		# choos selector
		pos = i.find('{')
		sel = i[0:pos]
		block = i[pos:]
		selector = parse_selector(sel)
		selector.properties = parse_properties(block)
		selectors.append( selector )
	return selectors

# parse selector rules
def parse_selector(text):
	# selector result
	selector = None
	print "selector: ", text
	
	# first split them into groups
	group = text.split(',')
	for i in xrange(len(group)):
		group[i] = group[i].strip()
	
	if len(group) == 1:
		# single selector
		selector = parse_pseudo(group[0])
	else:
		grouped = []
		# grouped selector
		for i in group:
			grouped.append( parse_pseudo(i) )
		selector = group_selector(grouped)
	
	return selector


# parse pseudo selector
def parse_pseudo(text):
	print "pseudo: ", text
	pos = text.find(':')
	if pos == -1:
		# no pseudo selector
		return parse_context(text)
	else:
		sel = text[0:pos]
		pseudo = text[pos+1:]
		# pass to next selector
		sel = parse_context(sel)
		return pseudo_selector(sel,pseudo)


# identifier matching patter
ID_PATTERN = '([a-zA-Z\_\#\.][a-zA-Z0-9\_\-\|]*)\s*(\[[^\]]*\])?'
_re_context = re.compile(ID_PATTERN)

# parse context selectors
def parse_context(text):
	print "context: ", text
	global _re_context
	context = _re_context.findall(text)
	if len(context) == 1:
		# no context
		return parse_detail(*context[0])
	else:
		seq = []
		# context related
		for i in context:
			seq.append( parse_detail(*i) )
		return context_selector(seq)

# parse detail
def parse_detail(text,detail):
	if detail == '':
		return parse_element(text)
		
	# explain matching rule
	detail = detail[1:-1].strip()
	pos = detail.find('=')
	if detail[pos-1] in ( '^' , '$' , '*' ):
		prop = detail[0:pos-1].strip()
		match = detail[pos-1:pos].strip()
		expr = detail[pos+1:].replace('"','').strip()
	else:
		prop = detail[0:pos].strip()
		match = None
		expr = detail[pos+1:].replace('"','').strip()
	# create selector
	selector = parse_element(text)
	return rule_selector(selector,prop,match,expr)


# parse selector
def parse_element(name):
	# detemine by starter
	if name.startswith('#'):
		# id selector
		return id_selector(name[1:])
	elif name.startswith('.'):
		# class selector
		return class_selector(name[1:])
	else:
		# element selector
		return element_selector(name)


# parse properties
def parse_properties(block):
	# decompose block
	block = block[1:-1].strip()

	# empty block detected
	if block == "":
		return

	properties = []
	texts = block.split(';')
	for i in texts:
		i = i.strip()
		if len(i)>0:
			properties.append( parse_property(i) )
	return properties

# parse property entry
def parse_property(text):
	#split
	important = text.endswith('!important')
	pos = text.find(':')
	name = text[0:pos].strip()
	value = text[pos+1:].replace('!important','').strip()
	return (name,value,important)
